import { $set } from '@feng3d/serialization';
import { BufferGeometry3D, Color, LineBasicMaterial3D, MathUtils3D, MeshLambertMaterial3D, Node3D, PerspectiveCamera3D, Scene3D, StatsComponent, TextureLoader3D, Vector3, Vector4, WebGLRenderer3D } from 'feng3d-three';
import { ParametricGeometry3D } from '../../extension/geometries/ParametricGeometry3D';
import { NURBSCurve, NURBSSurface } from '../../extension/thirdparty/three/imports';

const rootPath = '../../';

let container: HTMLDivElement;
let stats: StatsComponent;

let camera: PerspectiveCamera3D;
let scene: Scene3D;
let renderer: WebGLRenderer3D;
let group: Node3D;

let targetRotation = 0;
let targetRotationOnPointerDown = 0;

let pointerX = 0;
let pointerXOnPointerDown = 0;

let windowHalfX = window.innerWidth / 2;

init();
animate();

function init()
{
    container = document.createElement('div');
    document.body.appendChild(container);

    camera = new Node3D().addComponent('PerspectiveCamera3D', { fov: 50, aspect: window.innerWidth / window.innerHeight, near: 1, far: 2000 });
    camera.entity.position.set(0, 150, 750);

    scene = new Node3D().addComponent('Scene3D');
    scene.background = new Color(0xf0f0f0);

    scene.addComponent('AmbientLight3D', { color: { value: 0x808080 } });

    const light = new Node3D().addComponent('DirectionalLight3D', { color: { value: 0xffffff }, intensity: 1 });
    light.entity.position.set(1, 1, 1);
    scene.entity.addChild(light.entity);

    group = new Node3D();
    group.position.y = 50;
    scene.entity.addChild(group);

    // NURBS curve

    const nurbsControlPoints: Vector4[] = [];
    const nurbsKnots: number[] = [];
    const nurbsDegree = 3;

    for (let i = 0; i <= nurbsDegree; i++)
    {
        nurbsKnots.push(0);
    }

    for (let i = 0, j = 20; i < j; i++)
    {
        nurbsControlPoints.push(
            new Vector4(
                Math.random() * 400 - 200,
                Math.random() * 400,
                Math.random() * 400 - 200,
                1 // weight of control point: higher means stronger attraction
            )
        );

        const knot = (i + 1) / (j - nurbsDegree);
        nurbsKnots.push(MathUtils3D.clamp(knot, 0, 1));
    }

    const nurbsCurve = new NURBSCurve(nurbsDegree, nurbsKnots, nurbsControlPoints, undefined, undefined);

    const nurbsGeometry = new BufferGeometry3D();
    nurbsGeometry.setFromPoints(nurbsCurve.getPoints(200));

    const nurbsMaterial = new LineBasicMaterial3D({ color: 0x333333 });

    const nurbsLine = new Node3D().addComponent('Line3D', { geometry: nurbsGeometry, material: nurbsMaterial });
    nurbsLine.entity.position.set(200, -100, 0);
    group.addChild(nurbsLine.entity);

    const nurbsControlPointsGeometry = new BufferGeometry3D();
    nurbsControlPointsGeometry.setFromPoints(nurbsCurve['controlPoints']);

    const nurbsControlPointsMaterial = new LineBasicMaterial3D({ color: 0x333333, opacity: 0.25, transparent: true });

    const nurbsControlPointsLine = new Node3D().addComponent('Line3D', { geometry: nurbsControlPointsGeometry, material: nurbsControlPointsMaterial });
    nurbsControlPointsLine.entity.position.copy(nurbsLine.entity.position);
    group.addChild(nurbsControlPointsLine.entity);

    // NURBS surface

    const nsControlPoints = [
        [
            new Vector4(-200, -200, 100, 1),
            new Vector4(-200, -100, -200, 1),
            new Vector4(-200, 100, 250, 1),
            new Vector4(-200, 200, -100, 1)
        ],
        [
            new Vector4(0, -200, 0, 1),
            new Vector4(0, -100, -100, 5),
            new Vector4(0, 100, 150, 5),
            new Vector4(0, 200, 0, 1)
        ],
        [
            new Vector4(200, -200, -100, 1),
            new Vector4(200, -100, 200, 1),
            new Vector4(200, 100, -250, 1),
            new Vector4(200, 200, 100, 1)
        ]
    ];
    const degree1 = 2;
    const degree2 = 3;
    const knots1 = [0, 0, 0, 1, 1, 1];
    const knots2 = [0, 0, 0, 0, 1, 1, 1, 1];
    const nurbsSurface = new NURBSSurface(degree1, degree2, knots1, knots2, nsControlPoints);

    const map = new TextureLoader3D().load(`${rootPath}textures/uv_grid_opengl.jpg`);
    map.wrapS = map.wrapT = 'RepeatWrapping';
    map.anisotropy = 16;

    function getSurfacePoint(u: number, v: number, target: Vector3)
    {
        return nurbsSurface.getPoint(u, v, target);
    }

    const geometry = $set(new ParametricGeometry3D(), { func: getSurfacePoint, slices: 20, stacks: 20 });
    const material = $set(new MeshLambertMaterial3D(), { map, side: 'DoubleSide' });
    const object = new Node3D().addComponent('Mesh3D', { geometry, material });
    object.entity.position.set(-200, 100, 0);
    object.entity.scale.multiplyScalar(1);
    group.addChild(object.entity);

    //

    renderer = scene.addComponent('WebGLRenderer3D', { parameters: { antialias: true } });
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    container.appendChild(renderer.domElement);

    stats = scene.addComponent('StatsComponent');
    container.appendChild(stats.dom);

    container.style.touchAction = 'none';
    container.addEventListener('pointerdown', onPointerDown);

    //

    window.addEventListener('resize', onWindowResize);
}

function onWindowResize()
{
    windowHalfX = window.innerWidth / 2;

    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

    renderer.setSize(window.innerWidth, window.innerHeight);
}

//

function onPointerDown(event)
{
    if (event.isPrimary === false) return;

    pointerXOnPointerDown = event.clientX - windowHalfX;
    targetRotationOnPointerDown = targetRotation;

    document.addEventListener('pointermove', onPointerMove);
    document.addEventListener('pointerup', onPointerUp);
}

function onPointerMove(event: PointerEvent)
{
    if (event.isPrimary === false) return;

    pointerX = event.clientX - windowHalfX;

    targetRotation = targetRotationOnPointerDown + (pointerX - pointerXOnPointerDown) * 0.02;
}

function onPointerUp(event: PointerEvent)
{
    if (event.isPrimary === false) return;

    document.removeEventListener('pointermove', onPointerMove);
    document.removeEventListener('pointerup', onPointerUp);
}

//

function animate()
{
    requestAnimationFrame(animate);

    render();
    stats.update();
}

function render()
{
    group.rotation.y += (targetRotation - group.rotation.y) * 0.05;
    renderer.render(scene, camera);
}
